/**
 * elancom Corporation copy right 2012 - 2015  
 *   
 * $Header: https://192.168.1.224/svn/elancom/product/elancom-platform/trunk/src/main/java/com/elancom/framework/commons/json/parser/deserializer/ThrowableDeserializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Id: ThrowableDeserializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Author: lanzhouxue $
 * $Date: 2012-07-02 11:09:12 +0800 (Mon, 02 Jul 2012) $
 * $Revision: 1034 $
 */
package com.elancom.framework.commons.json.parser.deserializer;

import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import com.elancom.framework.commons.json.JSONException;
import com.elancom.framework.commons.json.parser.DefaultJSONParser;
import com.elancom.framework.commons.json.parser.Feature;
import com.elancom.framework.commons.json.parser.JSONScanner;
import com.elancom.framework.commons.json.parser.JSONToken;
import com.elancom.framework.commons.json.parser.ParserConfig;
import com.elancom.framework.commons.json.util.TypeUtils;

public class ThrowableDeserializer extends JavaBeanDeserializer {

	public ThrowableDeserializer(ParserConfig mapping, Class<?> clazz) {
		super(mapping, clazz);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
		final JSONScanner lexer = (JSONScanner) parser.getLexer();

		if (parser.getResolveStatus() == DefaultJSONParser.TypeNameRedirect) {
			parser.setResolveStatus(DefaultJSONParser.NONE);
		} else {
			if (lexer.token() != JSONToken.LBRACE) {
				throw new JSONException("syntax error");
			}
		}

		Throwable cause = null;
		Class<?> exClass = null;

		if (type != null && type instanceof Class) {
			final Class<?> clazz = (Class<?>) type;
			if (Throwable.class.isAssignableFrom(clazz)) {
				exClass = clazz;
			}
		}

		String message = null;
		StackTraceElement[] stackTrace = null;
		final Map<String, Object> otherValues = new HashMap<String, Object>();

		for (;;) {
			// lexer.scanSymbol
			final String key = lexer.scanSymbol(parser.getSymbolTable());

			if (key == null) {
				if (lexer.token() == JSONToken.RBRACE) {
					lexer.nextToken(JSONToken.COMMA);
					break;
				}
				if (lexer.token() == JSONToken.COMMA) {
					if (lexer.isEnabled(Feature.AllowArbitraryCommas)) {
						continue;
					}
				}
			}

			lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);

			if ("@type".equals(key)) {
				if (lexer.token() == JSONToken.LITERAL_STRING) {
					final String exClassName = lexer.stringVal();
					exClass = TypeUtils.loadClass(exClassName);
				} else {
					throw new JSONException("syntax error");
				}
				lexer.nextToken(JSONToken.COMMA);
			} else if ("message".equals(key)) {
				if (lexer.token() == JSONToken.NULL) {
					message = null;
				} else if (lexer.token() == JSONToken.LITERAL_STRING) {
					message = lexer.stringVal();
				} else {
					throw new JSONException("syntax error");
				}
				lexer.nextToken();
			} else if ("cause".equals(key)) {
				cause = deserialze(parser, null, "cause");
			} else if ("stackTrace".equals(key)) {
				stackTrace = parser.parseObject(StackTraceElement[].class);
			} else {
				// TODO
				otherValues.put(key, parser.parse());
			}

			if (lexer.token() == JSONToken.COMMA) {
				continue;
			}

			if (lexer.token() == JSONToken.RBRACE) {
				lexer.nextToken(JSONToken.COMMA);
				break;
			}
		}

		Throwable ex = null;
		if (exClass == null) {
			ex = new Exception(message, cause);
		} else {
			try {
				ex = createException(message, cause, exClass);
				if (ex == null) {
					ex = new Exception(message, cause);
				}
			} catch (final Exception e) {
				throw new JSONException("create instance error", e);
			}
		}

		if (stackTrace != null) {
			ex.setStackTrace(stackTrace);
		}

		return (T) ex;
	}

	private Throwable createException(String message, Throwable cause, Class<?> exClass) throws Exception {
		Constructor<?> defaultConstructor = null;
		Constructor<?> messageConstructor = null;
		Constructor<?> causeConstructor = null;
		for (final Constructor<?> constructor : exClass.getConstructors()) {
			if (constructor.getParameterTypes().length == 0) {
				defaultConstructor = constructor;
				continue;
			}

			if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == String.class) {
				messageConstructor = constructor;
				continue;
			}

			if (constructor.getParameterTypes().length == 2 && constructor.getParameterTypes()[0] == String.class
					&& constructor.getParameterTypes()[1] == Throwable.class) {
				causeConstructor = constructor;
				continue;
			}
		}

		if (causeConstructor != null) {
			return (Throwable) causeConstructor.newInstance(message, cause);
		}

		if (messageConstructor != null) {
			return (Throwable) messageConstructor.newInstance(message);
		}

		if (defaultConstructor != null) {
			return (Throwable) defaultConstructor.newInstance();
		}

		return null;
	}

	@Override
	public int getFastMatchToken() {
		return JSONToken.LBRACE;
	}
}
